/*! \file
 * \brief Enter a brief description here.
 *
 * Enter a detailed description here. (optional)
 * \author Author (Ivo Wingelaar)
 * \date Date (2011)
 * \version Version (0.0.1)
 * \copyright Copyright (GNU LGPL v3)
 */

#include "LynxImage_C.hpp"
#include "LynxBlit.hpp"
#include "LynxColorConverter.hpp"

namespace Lynx
{

//! Constructor of empty image
Image_C::Image_C(E_COLOR_FORMAT format, const Myth::Math::Vector2u size) :
    mData(0),
    mSize(size),
    mFormat(format),
    mDeleteMemory(true)
{
    init_data();
}


//! Constructor from raw data
Image_C::Image_C(E_COLOR_FORMAT format, const Myth::Math::Vector2u size, void* data, bool ownForeignMemory, bool deleteForeignMemory) :
    mData(0),
    mSize(size),
    mFormat(format),
    mDeleteMemory(deleteForeignMemory)
{
    if(ownForeignMemory)
    {
        mData = (uint8_t*)0xbadf00d;
        init_data();
        mData = (uint8_t*)data;
    }
    else
    {
        mData = 0;
        init_data();
        memcpy(mData, data, mSize.Y * mPitch);
    }
}

//! assumes format and size has been set and creates the rest
void Image_C::init_data()
{
    mBytesPerPixel = get_bits_per_pixel_from_format(mFormat) / 8;

    // Pitch should be aligned...
    mPitch = mBytesPerPixel * mSize.X;

    if(!mData)
        mData = new uint8_t[mSize.Y * mPitch];
}


//! destructor
Image_C::~Image_C()
{
    if(mDeleteMemory)
        delete []mData;
}

//! Lock function.
void* Image_C::get_pointer()
{
    return mData;
}

//! Returns width and height of image data.
Myth::Math::Vector2u Image_C::get_dimension() const
{
    return mSize;
}


//! Returns bits per pixel.
uint32_t Image_C::get_bits_per_pixel() const
{
    return get_bits_per_pixel_from_format(mFormat);
}


//! Returns bytes per pixel
uint32_t Image_C::get_bytes_per_pixel() const
{
    return mBytesPerPixel;
}


//! Returns image data size in bytes
uint32_t Image_C::get_image_data_size_in_bytes() const
{
    return mPitch * mSize.Y;
}


//! Returns image data size in pixels
uint32_t Image_C::get_image_data_size_in_pixels() const
{
    return mSize.X * mSize.Y;
}


//! returns mask for red value of a pixel
uint32_t Image_C::get_red_mask() const
{
    switch(mFormat)
    {
    case ECF_A1R5G5B5:
        return 0x1F<<10;
    case ECF_R5G6B5:
        return 0x1F<<11;
    case ECF_R8G8B8:
        return 0x00FF0000;
    case ECF_A8R8G8B8:
        return 0x00FF0000;
    default:
        return 0x0;
    }
}


//! returns mask for green value of a pixel
uint32_t Image_C::get_green_mask() const
{
    switch(mFormat)
    {
    case ECF_A1R5G5B5:
        return 0x1F<<5;
    case ECF_R5G6B5:
        return 0x3F<<5;
    case ECF_R8G8B8:
        return 0x0000FF00;
    case ECF_A8R8G8B8:
        return 0x0000FF00;
    default:
        return 0x0;
    }
}


//! returns mask for blue value of a pixel
uint32_t Image_C::get_blue_mask() const
{
    switch(mFormat)
    {
    case ECF_A1R5G5B5:
        return 0x1F;
    case ECF_R5G6B5:
        return 0x1F;
    case ECF_R8G8B8:
        return 0x000000FF;
    case ECF_A8R8G8B8:
        return 0x000000FF;
    default:
        return 0x0;
    }
}


//! returns mask for alpha value of a pixel
uint32_t Image_C::get_alpha_mask() const
{
    switch(mFormat)
    {
    case ECF_A1R5G5B5:
        return 0x1<<15;
    case ECF_R5G6B5:
        return 0x0;
    case ECF_R8G8B8:
        return 0x0;
    case ECF_A8R8G8B8:
        return 0xFF000000;
    default:
        return 0x0;
    }
}


//! sets a pixel
void Image_C::set_pixel(uint32_t x, uint32_t y, const Color color, bool blend)
{
    if(x >= mSize.X || y >= mSize.Y)
        return;

    switch(mFormat)
    {
    case ECF_A1R5G5B5:
    {
        uint16_t* dest = (uint16_t*)(mData + (y * mPitch) + (x << 1));
        *dest = a8r8g8b8_to_a1r5g5b5(color.mColor);
    }
    break;

    case ECF_R5G6B5:
    {
        uint16_t* dest = (uint16_t*)(mData + (y * mPitch) + (x << 1));
        *dest = a8r8g8b8_to_r5g6b5(color.mColor);
    }
    break;

    case ECF_R8G8B8:
    {
        uint8_t* dest = mData + (y * mPitch) + (x * 3);
        dest[0] = (uint8_t)color.get_red();
        dest[1] = (uint8_t)color.get_green();
        dest[2] = (uint8_t)color.get_blue();
    }
    break;

    case ECF_A8R8G8B8:
    {
        uint32_t* dest = (uint32_t*)(mData + (y * mPitch) + (x << 2));
        *dest = blend ? PixelBlend32(*dest, color.mColor) : color.mColor;
    }
    break;
    }
}


//! returns a pixel
Color Image_C::get_pixel(uint32_t x, uint32_t y) const
{
    if(x >= mSize.X || y >= mSize.Y)
        return Color(0);

    switch(mFormat)
    {
    case ECF_A1R5G5B5:
        return a1r5g5b5_to_a8r8g8b8(((uint16_t*)mData)[y*mSize.X + x]);
    case ECF_R5G6B5:
        return r5g6b5_to_a8r8g8b8(((uint16_t*)mData)[y*mSize.X + x]);
    case ECF_A8R8G8B8:
        return ((uint32_t*)mData)[y*mSize.X + x];
    case ECF_R8G8B8:
    {
        uint8_t* p = mData+(y*3)*mSize.X + (x*3);
        return Color(255,p[0],p[1],p[2]);
    }
    }

    return Color(0);
}


//! returns the color format
E_COLOR_FORMAT Image_C::get_color_format() const
{
    return mFormat;
}

uint32_t Image_C::get_pitch() const
{
    return mPitch;
}

//! copies this surface into another at given position
void Image_C::copy_to(Image* target, const Myth::Math::Vector2i& pos)
{
    Blit(BLITTER_TEXTURE, target, 0, &pos, this, 0, 0);
}


//! copies this surface partially into another at given position
void Image_C::copy_to(Image* target, const Myth::Math::Vector2i& pos, const Myth::Math::Rectanglei& sourceRect, const Myth::Math::Rectanglei* clipRect)
{
    Blit(BLITTER_TEXTURE, target, clipRect, &pos, this, &sourceRect, 0);
}


//! copies this surface into another, using the alpha mask, a cliprect and a color to add with
void Image_C::copy_to_with_alpha(Image* target, const Myth::Math::Vector2i& pos, const Myth::Math::Rectanglei& sourceRect, const Color color, const Myth::Math::Rectanglei* clipRect)
{
    // color blend only necessary on not full spectrum aka. color.color != 0xFFFFFFFF
    Blit(color.mColor == 0xFFFFFFFF ? BLITTER_TEXTURE_ALPHA_BLEND: BLITTER_TEXTURE_ALPHA_COLOR_BLEND, target, clipRect, &pos, this, &sourceRect, color.mColor);
}


//! copies this surface into another, scaling it to the target image size
// note: this is very very slow.
void Image_C::copy_to_scaling(void* target, uint32_t width, uint32_t height, E_COLOR_FORMAT format, uint32_t pitch)
{
    if(!target || !width || !height)
        return;

    const uint32_t bpp = get_bits_per_pixel_from_format(format)/8;
    if(0 == pitch)
        pitch = width*bpp;

    if(mFormat == format && mSize.X == width && mSize.Y==height)
    {
        if(pitch==mPitch)
        {
            memcpy(target, mData, height*pitch);
            return;
        }
        else
        {
            uint8_t* tgtpos = (uint8_t*) target;
            uint8_t* srcpos = mData;
            const uint32_t bwidth = width*bpp;
            const uint32_t rest = pitch-bwidth;
            for(uint32_t y=0; y<height; ++y)
            {
                // copy scanline
                memcpy(tgtpos, srcpos, bwidth);
                // clear pitch
                memset(tgtpos+bwidth, 0, rest);
                tgtpos += pitch;
                srcpos += mPitch;
            }
            return;
        }
    }

    const float sourceXStep = (float)mSize.X / (float)width;
    const float sourceYStep = (float)mSize.Y / (float)height;
    int32_t yval=0, syval=0;
    float sy = 0.0f;
    for(uint32_t y=0; y<height; ++y)
    {
        float sx = 0.0f;
        for(uint32_t x=0; x<width; ++x)
        {
            ColorConverter::convert_viaFormat(mData+ syval + ((int32_t)sx)*mBytesPerPixel, mFormat, 1, ((uint8_t*)target)+ yval + (x*bpp), format);
            sx+=sourceXStep;
        }
        sy+=sourceYStep;
        syval=((int32_t)sy)*mPitch;
        yval+=pitch;
    }
}


//! copies this surface into another, scaling it to the target image size
// note: this is very very slow.
void Image_C::copy_to_scaling(Image* target)
{
    if(!target)
        return;

    const Myth::Math::Vector2u targetSize = target->get_dimension();

    if(targetSize==mSize)
    {
        copy_to(target);
        return;
    }

    copy_to_scaling(target->get_pointer(), targetSize.X, targetSize.Y, target->get_color_format());
}


//! copies this surface into another, scaling it to fit it.
void Image_C::copy_to_scaling_box_filter(Image* target, int32_t bias, bool blend)
{
    const Myth::Math::Vector2u destSize = target->get_dimension();

    const float sourceXStep = (float) mSize.X / (float) destSize.X;
    const float sourceYStep = (float) mSize.Y / (float) destSize.Y;

    target->get_pointer();

    int32_t fx = Myth::Math::ceil(sourceXStep);
    int32_t fy = Myth::Math::ceil(sourceYStep);
    float sx;
    float sy;

    sy = 0.f;
    for(uint32_t y = 0; y != destSize.Y; ++y)
    {
        sx = 0.f;
        for(uint32_t x = 0; x != destSize.X; ++x)
        {
            target->set_pixel(x, y, get_pixel_box(Myth::Math::floor(sx), Myth::Math::floor(sy), fx, fy, bias), blend);
            sx += sourceXStep;
        }
        sy += sourceYStep;
    }
}


//! fills the surface with given color
void Image_C::fill(const Color color)
{
    uint32_t c;

    switch(mFormat)
    {
    case ECF_A1R5G5B5:
        c = color.to_a1r5g5b5();
        c |= c << 16;
        break;
    case ECF_R5G6B5:
        c = a8r8g8b8_to_r5g6b5(color.mColor);
        c |= c << 16;
        break;
    case ECF_A8R8G8B8:
        c = color.mColor;
        break;
    case ECF_R8G8B8:
    {
        uint8_t rgb[3];
        ColorConverter::convert_A8R8G8B8toR8G8B8(&color, 1, rgb);
        const uint32_t size = get_image_data_size_in_bytes();
        for(uint32_t i=0; i<size; i+=3)
        {
            memcpy(mData+i, rgb, 3);
        }
        return;
    }
    break;
    }
    if(mFormat != ECF_A1R5G5B5 && mFormat != ECF_R5G6B5 &&
            mFormat != ECF_A8R8G8B8)
        return;

    memset32(mData, c, get_image_data_size_in_bytes());
}

//! get a filtered pixel
inline Color Image_C::get_pixel_box(int32_t x, int32_t y, int32_t fx, int32_t fy, int32_t bias) const
{
    Color c;
    int32_t a = 0, r = 0, g = 0, b = 0;

    for(int32_t dx = 0; dx != fx; ++dx)
    {
        for(int32_t dy = 0; dy != fy; ++dy)
        {
            c = get_pixel(Myth::Math::min(x + dx, (int32_t)mSize.X - 1) ,
                          Myth::Math::min(y + dy, (int32_t)mSize.Y - 1)
                         );

            a += c.get_alpha();
            r += c.get_red();
            g += c.get_green();
            b += c.get_blue();
        }

    }

    int32_t sdiv = s32_log2_s32(fx * fy);

    a = Myth::Math::clamp((a >> sdiv) + bias, 0, 255);
    r = Myth::Math::clamp((r >> sdiv) + bias, 0, 255);
    g = Myth::Math::clamp((g >> sdiv) + bias, 0, 255);
    b = Myth::Math::clamp((b >> sdiv) + bias, 0, 255);

    c.set(a, r, g, b);
    return c;
}

} // namespace Lynx
